/*-
 * Copyright (C) 2020, Shanghai Trusted Industry Control Platform Co., Ltd.
 * Created by 周正辉 (Zhenghui Zhou) <zhouzhenghui@ticpsh.com>
 */

#include "dependency_graph.h"

#include <llvm/Support/Path.h>

using namespace clang;
using njson = nlohmann::json;

namespace rocket {
namespace testif {

DependencyGraph::DependencyGraph(njson &includes, const clang::Preprocessor &pp,
                                 const std::string &main_file,
                                 const std::string &sysroot)
    : src_mgr(pp.getSourceManager()),
      missing(includes["missing"]),
      files(includes["files"]),
      main_file(main_file),
      sysroot(sysroot) {
  missing = njson::object();
  files = njson::object();
  llvm::SmallString<256> path;
  llvm::sys::fs::current_path(path);
  root = path.c_str();
}

void DependencyGraph::InclusionDirective(
    clang::SourceLocation hash_loc, const clang::Token &include_token,
    llvm::StringRef filename, bool is_angled,
    clang::CharSourceRange filename_range, const clang::FileEntry *file,
    llvm::StringRef search_path, llvm::StringRef relative_path,
    const clang::Module *imported,
    clang::SrcMgr::CharacteristicKind file_type) {
  if (!file) {
    std::string name(filename);
    if (is_angled) {
      name = "<" + filename.str() + ">";
    }
    njson &dependency = missing[std::move(name)];
    addDependencyLocation(dependency["froms"], hash_loc, filename_range);
  } else {
    njson &dependency = files[makeCanonicalPath(file)];
    if (dependency.is_null()) {
      dependency = njson::object();
      njson &attributes = dependency["@attributes"];
      if (attributes.is_null()) {
        attributes = njson::array();
      }
      if (file_type != SrcMgr::C_User &&
          file_type != SrcMgr::C_User_ModuleMap) {
        attributes.push_back("isSystem");
      }
      if (file_type == SrcMgr::C_ExternCSystem) {
        attributes.push_back("isExternC");
      }
    }
    addDependencyLocation(dependency["froms"], hash_loc, filename_range);
  }
}

void DependencyGraph::addDependencyLocation(
    njson &froms, clang::SourceLocation hash_loc,
    clang::CharSourceRange filename_range) {
  if (froms.is_null()) {
    froms = njson::array();
  }
  const FileEntry *file_from = src_mgr.getFileEntryForID(
      src_mgr.getFileID(src_mgr.getExpansionLoc(hash_loc)));
  if (file_from) {
    njson j = njson::object();
    j["file"] = makeCanonicalPath(file_from);
    {
      njson location = njson::object();
      {
        njson begin;
        PresumedLoc presumed_loc =
            src_mgr.getPresumedLoc(filename_range.getBegin());
        begin["line"] = std::to_string(presumed_loc.getLine());
        begin["column"] = std::to_string(presumed_loc.getColumn());
        location["begin"] = std::move(begin);
      }
      {
        njson end;
        PresumedLoc presumed_loc =
            src_mgr.getPresumedLoc(filename_range.getEnd());
        end["line"] = std::to_string(presumed_loc.getLine());
        end["column"] = std::to_string(presumed_loc.getColumn());
        location["end"] = std::move(end);
      }
      j["location"] = std::move(location);
    }
    froms.push_back(std::move(j));
  }
}

std::string DependencyGraph::makeCanonicalPath(const clang::FileEntry *file) {
  std::string name;
  auto FE = src_mgr.getFileEntryForID(src_mgr.getMainFileID());
  if (FE && FE->getUID() == file->getUID()) {
    name = main_file;
  } else {
#if LLVM_VERSION_MAJOR > 18
    name = (std::string)file->tryGetRealPathName();
#define startswith starts_with
#else
    name = (std::string)file->getName();
#endif
    if (StringRef(name).startswith(root)) {
      llvm::SmallString<256> path(name);
      llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
      if (StringRef(path).startswith(root)) {
        name = (std::string)llvm::sys::path::relative_path(
            StringRef(path).substr(root.size()));
      } else {
        name = (std::string)llvm::sys::path::relative_path(
            name.substr(root.size()));
        llvm::SmallString<256> path(name);
        llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
        name = path.c_str();
      }
    } else {
      if (StringRef(name).startswith(sysroot)) {
        name = name.substr(sysroot.size());
        if (!llvm::sys::path::is_absolute(name)) {
          name = "/" + name;
        }
      }
      llvm::SmallString<256> path(name);
      llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
      name = path.c_str();
    }
  }
  return name;
}

}  // namespace testif
}  // namespace rocket
